#include "nemu.h"
#include "memory/cache.h"
#include <time.h>
#include "burst.h"
#include "stdlib.h"

uint32_t dram_read(hwaddr_t addr,size_t len);
void call_ddr3_read(hwaddr_t,void *);
void call_ddr3_write(hwaddr_t,void *,uint8_t *);

void init_cache(){
	int i;
	for(i=0;i<(CACHE_SIZE_F/CACHE_BLOCK_SIZE);i++) {
		caf[i].Valid=false;
		caf[i].tag=0;
                memset(caf[i].data,0,CACHE_BLOCK_SIZE);
	}
	for(i=0;i<(CACHE_SIZE_S/CACHE_BLOCK_SIZE);i++) {
		cas[i].Valid=false;
		cas[i].dirty=false;
		cas[i].tag=0;
		memset(cas[i].data,0,CACHE_BLOCK_SIZE);
	}
}

uint32_t cache_read_f(hwaddr_t addr) {
        uint32_t group=(addr>>CACHE_BLOCK_BIT)&0x7f;
	bool hit=false;
	int i;
	for(i=CACHE_WAY_BIT_F*group;i<(group+1)*CACHE_WAY_BIT_F;i++) {
		if(caf[i].tag==(addr>>(CACHE_BLOCK_BIT+CACHE_GROUP_BIT))&&caf[i].Valid) {
			hit=true;
			break;
		}
	}

	if(!hit) {
		int j=cache_read_s(addr);
		for(i=group*CACHE_WAY_BIT_F;i<(group+1)*CACHE_WAY_BIT_F;i++) {
			if(!caf[i].Valid) break;
		}
		if(i==(group+1)*CACHE_WAY_BIT_F) {
			srand(0);
			i=group*CACHE_WAY_BIT_F+rand()%CACHE_WAY_BIT_F;
		}
		caf[i].Valid=true;
		caf[i].tag=addr>>(CACHE_BLOCK_BIT+CACHE_GROUP_BIT);
		memcpy(caf[i].data,cas[j].data,CACHE_BLOCK_SIZE);
	}
	return i;
}

uint32_t cache_read_s(hwaddr_t addr) {
        uint32_t group=(addr>>CACHE_BLOCK_BIT)&((1<<12)-1);
        uint32_t block=(addr>>6)<<6;
	int i;
	bool hit=false;
	for(i=group*CACHE_WAY_BIT_S;i<(group+1)*CACHE_WAY_BIT_S;i++) {
		if(cas[i].tag==(addr>>18)&&cas[i].Valid) {
			hit=true;
			break;
		}
	}
	if(!hit) {
		int j;
		for(i=group*CACHE_WAY_BIT_S;i<(group+1)*CACHE_WAY_BIT_S;i++) {
			if(!cas[i].Valid)
				break;
		}
		if(i==(group+1)*CACHE_WAY_BIT_S) {
			srand(0);
			i=group*CACHE_WAY_BIT_S+rand()%CACHE_WAY_BIT_S;
			if(cas[i].dirty) {
				uint8_t mask[BURST_LEN*2];
				memset(mask,1,BURST_LEN*2);
				for(j=0;j<CACHE_BLOCK_SIZE/BURST_LEN;j++)
					call_ddr3_write(block+j*BURST_LEN,cas[i].data+j*BURST_LEN,mask);
			}
		}
                
		cas[i].Valid=true;
		cas[i].tag=addr>>18;
		cas[i].dirty=false;
		for(j=0;j<BURST_LEN;j++) 
			call_ddr3_read(block+j*BURST_LEN,cas[i].data+j*BURST_LEN);
	}
	return i;

}

void cache_write_f(hwaddr_t addr,size_t len,uint32_t data) {
	uint32_t group=(addr>>CACHE_BLOCK_BIT)&0x7f;
	uint32_t offset=addr&(CACHE_BLOCK_SIZE-1);
	int i;
	bool hit=false;
	for(i=group*CACHE_WAY_BIT_F;i<(group+1)*CACHE_WAY_BIT_F;i++) {
		if(caf[i].tag==(addr>>(CACHE_BLOCK_BIT+CACHE_GROUP_BIT))&&caf[i].Valid) {
			hit=true;
			break;
		}
	}

	if(hit) 	
		memcpy(caf[i].data+offset,&data,len);
        cache_write_s(addr,len,data);
}

void cache_write_s(hwaddr_t addr,size_t len,uint32_t data) {
	uint32_t group=(addr>>CACHE_BLOCK_BIT)&((1<<12)-1);
	uint32_t offset=addr&(CACHE_BLOCK_SIZE-1);
	bool hit=false;
	int i;
	for(i=group*CACHE_WAY_BIT_S;i<(group+1)*CACHE_WAY_BIT_S;i++) {
		if(cas[i].tag==(addr>>(CACHE_BLOCK_BIT+CACHE_GROUP_BIT))&&cas[i].Valid) {
			hit=true;
			break;
		}
	}
	if(!hit) 
		i=cache_read_s(addr);
	cas[i].dirty=true;
	memcpy(cas[i].data+offset,&data,len);
}
